/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package sd_proj1.monSolution;

import java.util.TreeMap;
import sd_proj1.interfaces.IClient;
import sd_proj1.interfaces.IntDonaFifo;
import sd_proj1.interfaces.IDonaLoja;

/**
 * Esta classe MonitorLoja cria um objecto do tipo loja onde vão interagir a dona e os clientes.
 * Neste monitor numa versão prévia foi incluído um array de estados do cliente, que depois podia ter sido retirada
 * mas optou-se por continuar com este array para nos podermos guiar ao longo do programa.
 * @author Pedro Correia
 * @author Valter Vicente
 */
public class MonitorLoja implements IClient, IDonaLoja {

    /**
     *  Este tipo de dados implementa a Loja - é aqui que tem lugar toda a comunicação.
     *  Solução concorrente baseada em monitores como elemento de sincronização entre os threads cliente e DonaLoja.
     */
    /**
     *  Constantes que caracterizam o estado interno dos threads cliente
     */
    public enum stateClient {

	/**
	 *  estado independente na simulação, o cliente deve ser posto a dormir durante um intervalo de tempo aleatório
	 */
	CARRYING_OUT_DAILY_CHORES,
	/**
	 * estado de transição, verificação se a porta da loja está aberta
	 */
	CHECKING_DOOR_OPEN,
	/**
	 * estado de transição, verificação das prateleiras da loja por produtos
	 */
	APPRAISING_OFFER_IN_DISPLAY,
	/**
	 * estado de bloqueio o cliente é acordado pela dona da empresa quando completa a transacção, sayGoodByeToCustomer
	 */
	BUYING_SOME_GOODS
    }
    /**
     *  Constantes que caracterizam o estado interno dos threads cliente
     */
    /**
     * Estado da Loja
     * false - Fechada
     * true - Aberta
     */
    private boolean StateLoja = true;
    /**
     * Estado da dona
     */
    private IntDonaFifo stateDona;
    private TreeMap map;
    private int pecasCliente[];
    /**
     *  Número de donas presentes na loja
     * Neste momento
     *
     *    @serialField nDonaLoja
     */
    private int nDonaLoja = 0;
    /**
     *  Número de clientes que frequentam a loja
     *
     *    @serialField nCliente
     */
    private int nCliente = 0;
    /**
     * Número de clientes dentro da loja
     * @serialField nClienteInLoja
     */
    private int nClienteInLoja = 0;
    /**
     *  Estado presente dos clientes
     *
     *    @serialField stateClient
     */
    private stateClient[] stateCliente;
    /**
     * Número de peças na loja
     * @serialField nPecas
     */
    private int nPecas = 0;
    /**
     * Estado de funcionamento da thread dona da loja
     * @serialField donaIsDead
     */
    private boolean donaIsDead = false;

    /**
     *  Instanciação da loja.
     *
     *    @param nDonaLoja número de barbeiros presentes na barbearia
     *    @param nCliente número de clientes que frequentam a loja
     *    @param nPecas número de pecas no ínicio da loja
     *    @param stateDona Estado Geral onde se encontra o fifo de pedidos e o estado da dona da loja
     *    @param map Mapa onde vão ser guardados os registos de log
     */
    public MonitorLoja(int nDonaLoja, int nCliente, int nPecas, IntDonaFifo stateDona, TreeMap map) {
	/* fixar os parâmetros do problema */

	this.nDonaLoja = nDonaLoja;
	this.nPecas = nPecas;
	this.stateDona = stateDona;
	this.map = map;
	this.nCliente = nCliente;
	pecasCliente = new int[nCliente];

	Mensagem mlog = new Mensagem();

	mlog.setShopStat(nClienteInLoja, nPecas, 'F', 'F');
	map.put(Relogio.getInstance().getValue(), mlog);

	/* inicializar os estado interno */
	System.out.println("Dona OPENING THE SHOP");
	stateCliente = new stateClient[this.nCliente];


	for (int i = 0; i < this.nCliente; i++) {
	    stateCliente[i] = stateClient.CARRYING_OUT_DAILY_CHORES;
	    System.out.println("Estado do cliente" + i + " " + stateCliente[i].toString());
	    pecasCliente[i] = 0;

	}

    }

    /**
     *Método de finalização da thread cliente
     * @param clienteId
     * @return true quando o cliente pode morrer ou seja, quando se verifica a condição que
     * nao existe mais ninguem na loja, os artesaos morreram todos
     * e a dona está morta
     *
     */
    public synchronized boolean endOperCustomer(int clienteId) {
	if (isDonaIsDead()) /**Informação que a dona não vai recarregar mais peças*/
	{
	    return (true);
	} else {
	    if (nClienteInLoja == 0 && nPecas == 0 && stateDona.isAllCraftsDead() && !isDonaIsDead()) {
		stateDona.emptyRequest();
	    }
	}
	return (false);
    }

    /**
     *Método de finalização da thread dona da loja
     * @return true qundo a dona está em fase que pode morrer, ou seja quando os artesaos estao todos mortos, o nClientes na loja
     * é nulo e que nao existem mais peças para os mesmos comprarem
     */
    public synchronized boolean endOperEntrep() {
	if (stateDona.isAllCraftsDead() && nClienteInLoja == 0 && nPecas == 0) {
	    donaIsDead = true;

	    return (true);

	} else {
	    return (false);

	}
    }

    /**
     *  Definição das operações efectuadas sobre o monitor:
     *     - verificação por parte dos clientes se a loja se encontra aberta
     * @return false se a porta está fechada
     *         true se a porta está aberta
     */
    public synchronized boolean isDoorOpen() {
	if (StateLoja == true) {
	    return (true);

	} else {
	    return (false);

	}
    }

    /**
     * Monitor invocado pela dona da loja
     * value indicando que a loja foi fechada com sucesso
     */
    public synchronized void closeTheDoor() {
	Mensagem mlog = new Mensagem();
	this.StateLoja = false;
	System.out.println(" A DONA FECHOU A PORTA DA LOJA");
	mlog.setShopDoorStat(Mensagem.storeStat.SDCL);
	map.put(Relogio.getInstance().getValue(), mlog);
    }

    /**
     * Monitor invocado pela dona da Loja
     * Abertura da Loja por parte da dona.
     *
     */
    public synchronized void prepareToWork() {
	Mensagem mlog = new Mensagem();
	if (this.nPecas != 0) {
	    this.StateLoja = true;
	    mlog.setEntrepStat(Mensagem.eStat.WFNT);
	    mlog.setShopDoorStat(Mensagem.storeStat.SPOP);
	} else {
	    System.out.println(" A loja mantem-se fechada " + StateLoja);
	    mlog.setEntrepStat(Mensagem.eStat.WFNT);
	}
	map.put(Relogio.getInstance().getValue(), mlog);
    }

    /**
     *  O cliente verifica os produtos expostos na loja e escolhe um (operação interna).
     *
     * @param clienteId
     * @return result -> Valor de peças escolhidas pelo cliente
     */
    public synchronized int perusingAround(int clienteId) {
	Mensagem mlog = new Mensagem();
	int result = randint(0, nPecas);
	pecasCliente[clienteId] = pecasCliente[clienteId] + result;
	this.nPecas = nPecas - result;
	mlog.setShopStat(nClienteInLoja, nPecas, '0', '0');

	map.put(Relogio.getInstance().getValue(), mlog);
	return result;




    }

    /**
     * Monitor invocado pelo cliente, alteração do estado do cliente
     * Este cliente irá verificar o estado da porta em primeiro lugar
     * @param clienteId     
     */
    public synchronized void goShopping(int clienteId) {
	Mensagem mlog = new Mensagem();
	stateCliente[clienteId] = stateClient.CHECKING_DOOR_OPEN;
	mlog.setStatClient(Mensagem.clientStat.CSDO, 0, clienteId);
	map.put(Relogio.getInstance().getValue(), mlog);

    }

    /**
     * Monitor invocado pelo cliente, alteração do estado do cliente, este irá entrar na loja
     * e mudar o estado para appraising_offer in display.
     * @param clienteId
     */
    public synchronized void enterShop(int clienteId) {
	Mensagem mlog = new Mensagem();
	if (isDoorOpen()) {
	    nClienteInLoja += 1;
	    stateCliente[clienteId] = stateClient.APPRAISING_OFFER_IN_DISPLAY;
	    mlog.setStatClient(Mensagem.clientStat.AOID, pecasCliente[clienteId], clienteId);
	    mlog.setShopStat(nClienteInLoja, nPecas, '0', '0');

	} else {
	    stateCliente[clienteId] = stateClient.CARRYING_OUT_DAILY_CHORES;
	    mlog.setStatClient(Mensagem.clientStat.CODC, pecasCliente[clienteId], clienteId);
	    mlog.setShopStat(nClienteInLoja, nPecas, '0', '0');

	}
	map.put(Relogio.getInstance().getValue(), mlog);
    }

    /**
     * Monitor invocado pelo cliente, alteração do estado do cliente para saída da loja e volta à vida normal
     * @param clienteId
     */
    public synchronized void exitShop(int clienteId) {
	Mensagem mlog = new Mensagem();
	System.out.println("O cliente  " + clienteId + "  saiu da loja");
	nClienteInLoja -= 1;
	mlog.setStatClient(Mensagem.clientStat.CODC, pecasCliente[clienteId], clienteId);
	mlog.setShopStat(nClienteInLoja, nPecas, '0', '0');
	map.put(Relogio.getInstance().getValue(), mlog);
    }

    /*
     *  Definição das operações efectuadas sobre o monitor:
     *     - querer comprar peças
     *     - dormir
     *     - chamar o cliente
     *     - receber o pagamento pelo serviço prestado.
     */
    /**
     *  Operação de ir comprar alguma peça (originada pelo cliente).
     *
     *    @param clienteId identificação do cliente
     *	  @param ng número de peças desejadas da loja
     *
     *    @return <li> true, se conseguiu comprar o que desejava
     *            <li> false, em caso contrário
     */
    public synchronized boolean iWantThis(int clienteId, int ng) {
	Mensagem mlog = new Mensagem();
	stateCliente[clienteId] = stateClient.BUYING_SOME_GOODS;
	System.out.println("iWantThis - Estado do cliente" + clienteId + " " + stateCliente[clienteId].toString());

	while (stateCliente[clienteId] != stateClient.CARRYING_OUT_DAILY_CHORES) // aguarda a continuação das operações
	{
	    mlog.setStatClient(Mensagem.clientStat.BYSG, pecasCliente[clienteId], clienteId);
	    try {
		System.out.println("iWantThis - Estado do cliente " + clienteId + " - Esperando pela dona");
		wait();


	    } catch (InterruptedException e) {
	    }

	}
	System.out.println("iWantThis - fim " + clienteId);

	map.put(Relogio.getInstance().getValue(), mlog);
	return (true); // abandona a loja com a compra efectuada

    }

    /**
     * Monitor invocado pela dona, para alteração do estado do cliente e da dona
     * @param clienteId identificação do cliente que vai ser despachado pela dona
     */
    public synchronized void sayGoodByeToCustomer(int clienteId) {
	Mensagem mlog = new Mensagem();
	stateCliente[clienteId] = stateClient.CARRYING_OUT_DAILY_CHORES;                // sinaliza que o cliente vai prosseguir com a sua vida
	notifyAll();
	mlog.setEntrepStat(Mensagem.eStat.ATAC);
	mlog.setStatClient(Mensagem.clientStat.CODC, pecasCliente[clienteId], clienteId);
	System.out.println("sayGoodByeToCustomer - Dona desbloqueia cliente  " + clienteId + " com estado " + stateCliente[clienteId].toString());
	if (this.nPecas == 0) {
	    closeTheDoor();
	    System.out.println("serviceCustomer - LOJA FECHADA POR FALTA DE PRODUTOS");
	}
	map.put(Relogio.getInstance().getValue(), mlog);
    }

    /**
     * Monitor invocado pela dona para verificação da existencia de clientes na loja
     * @return boolean value. true
     */
    public synchronized boolean customersInTheShop() {
	if (nClienteInLoja != 0) {
	    return (true);

	} else {
	    return (false);

	}
    }

    /**
     *   Monitor invocado pela dona, alteração do estado informando que fechou a loja em direcção à oficina
     */
    public void prepareToLeave() {
	Mensagem mlog = new Mensagem();
	mlog.setEntrepStat(Mensagem.eStat.CLTS);
	mlog.setShopStat(nClienteInLoja, nPecas, '0', '0');
	map.put(Relogio.getInstance().getValue(), mlog);
    }

    /**
     * Monitor invocado pela dona, alteração do estado informando que regressou à loja
     */
    public synchronized void returnToShop() {
	Mensagem mlog = new Mensagem();
	int i;
	i = stateDona.getnPecasTransporte();


	if (i != 0) {
	    nPecas = nPecas + i;
	    mlog.setEntrepStat(Mensagem.eStat.OPTS);
	    mlog.setShopStat(nClienteInLoja, nPecas, '0', '0');
	    stateDona.setnPecasTransporte(0);


	} else {
	    mlog.setEntrepStat(Mensagem.eStat.OPTS);
	    mlog.setShopStat(nClienteInLoja, nPecas, '0', '0');

	}
	map.put(Relogio.getInstance().getValue(), mlog);
    }

    /**
     * Funçao auxiliar para cálculo de um valor random entre um intervalo definido por low e high
     * @param low
     * @param high
     * @return int value
     */
    private int randint(int low, int high) {
	return ((int) (((high - low) * (Math.random()) + 0.5)) + low);
    }

    /**
     * Verificação do estado de vida da dona por parte dos clientes para poderem efectuar a sua morte.
     * @return donaIsDead
     */
    public boolean isDonaIsDead() {
	return donaIsDead;
    }
}
